package org.yuxuan.command.api;

import java.util.function.BiFunction;
import java.util.function.Function;

import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.ComponentBuilder;
import org.bukkit.command.CommandSender;

import org.coherent.core.Command;
import static org.coherent.core.Command.*;
import org.coherent.core.Command.Completion;
import static org.coherent.core.Command.Completion.*;
import org.coherent.core.Command.Parameter;
import static org.coherent.core.Command.Parameter.*;
import org.coherent.core.Command.Flow;
import static org.coherent.core.Command.Flow.*;
import org.coherent.core.Command.Body;
import static org.coherent.core.Command.Body.*;
import org.coherent.core.Command.Binding;
import static org.coherent.core.Command.Binding.*;
import org.coherent.core.Command.Dispatcher;
import static org.coherent.core.Command.Dispatcher.*;
import org.coherent.core.Command.Action;
import static org.coherent.core.Command.Action.*;
import org.coherent.core.Command.Behavior;
import static org.coherent.core.Command.Behavior.*;

import org.jparsec.core.Parser;
import static org.jparsec.core.Parser.*;
import org.jparsec.core.Text;
import static org.jparsec.core.Text.*;
import org.jparsec.core.parser.Char;
import static org.jparsec.core.parser.Char.*;
import org.jparsec.core.parser.Combinator;
import static org.jparsec.core.parser.Combinator.*;

import static org.jparsec.core.Parser.Notation.*;

import org.monadium.core.data.Bottom;
import static org.monadium.core.data.Bottom.*;
import org.monadium.core.data.Either;
import static org.monadium.core.data.Either.*;
import org.monadium.core.data.Id;
import org.monadium.core.data.List;
import static org.monadium.core.data.List.*;
import org.monadium.core.data.Tuple;
import static org.monadium.core.data.Tuple.*;
import org.monadium.core.data.Unit;
import static org.monadium.core.data.Unit.*;

import static org.monadium.core.Notation.*;

public final class BukkitCommands {
	BukkitCommands() { }

	static final class HelpCommand {
		enum CommandSubtype {
			Handler, Stub
		}

		enum ParameterSubtype {
			Atomic, Nested, Union
		}

		static void sendTitle(CommandSender sender, String title) {
			sender.sendMessage(new ComponentBuilder()
				.append("---------").reset().color(ChatColor.YELLOW)
				.append(" ").reset()
				.append(title).reset()
				.append(" ").reset()
				.append("--------------------------------------".substring(Math.min(title.length(), 38))).reset().color(ChatColor.YELLOW)
				.create()
			);
		}

		static void sendEntry(CommandSender sender, String key, String entry) {
			sender.sendMessage(new ComponentBuilder()
				.append(key).reset().color(ChatColor.GOLD)
				.append(": ").reset().color(ChatColor.GOLD)
				.append(entry).reset()
				.create()
			);
		}

		static <S, C, A> CommandSubtype getSubtype(Command<S, C, A> command) {
			return command.match(new Command.Match<S, C, A, CommandSubtype>() {
				@Override public <P> CommandSubtype
				caseNode(Definition<S, C, P> definition, Dispatcher<S, C, P, A> dispatcher, Behavior<S, C, P, A> behavior) {
					return behavior.match(new Behavior.Match<S, C, P, A, CommandSubtype>() {
						@Override public CommandSubtype
						caseHandler(BiFunction<Context<S, C>, P, Action<A>> handler) {
							return CommandSubtype.Handler;
						}
						@Override public CommandSubtype
						caseStub() {
							return CommandSubtype.Stub;
						}
					});
				}
			});
		}

		static <S, C, A> ParameterSubtype getSubtype(Parameter<S, C, A> parameter) {
			return parameter.match(new Parameter.Match<S, C, A, ParameterSubtype>() {
				@Override public ParameterSubtype
				caseAtomic(Parser<Text, Context<S, C>, Bottom, A> parser, Parser<Text, Context<S, C>, Bottom, List<Completion>> completer) {
					return ParameterSubtype.Atomic;
				}
				@Override public <X> ParameterSubtype
				caseNested(Definition<S, C, X> definition, Function<X, Parser<Text, Context<S, C>, Bottom, A>> extend, Parser<Text, Context<S, C>, Bottom, List<Completion>> suggest) {
					return ParameterSubtype.Nested;
				}
				@Override public <X> ParameterSubtype
				caseUnion(List<Parameter<S, C, X>> parameters, Function<X, Parser<Text, Context<S, C>, Bottom, A>> extend, Parser<Text, Context<S, C>, Bottom, List<Completion>> suggest) {
					return ParameterSubtype.Union;
				}
			});
		}

		static <S, C, A> Tuple<List<Parameter<S, C, ?>>, A> inspectParameters(Body<S, C, Unit, A> body) {
			return body.match(new Body.Match<S, C, Unit, A, Tuple<List<Parameter<S, C, ?>>, A>>() {
				@Override public Tuple<List<Parameter<S, C, ?>>, A>
				caseEvaluate(A a) {
					return tuple(nil(), a);
				}
				@Override public <X> Tuple<List<Parameter<S, C, ?>>, A>
				caseDefine(Parameter<S, C, X> parameter, Id<Flow<Unit, X>, A> id) {
					return tuple(singleton(parameter), id.coerce(external(unit())));
				}
				@Override public <X> Tuple<List<Parameter<S, C, ?>>, A>
				caseFlatMap(Body<S, C, Unit, X> fx, Function<X, Body<S, C, Unit, A>> f) {
					Tuple<List<Parameter<S, C, ?>>, X> result1 = inspectParameters(fx);
					Tuple<List<Parameter<S, C, ?>>, A> result2 = inspectParameters(f.apply(result1.second()));
					return tuple(result1.first().concat(result2.first()), result2.second());
				}
			});
		}

		static <S, C> Command<S, C, ?> getCommand(Context<S, C> context) {
			return context.match(new Context.Match<S, C, Command<S, C, ?>>() {
				@Override public <A> Command<S, C, ?>
				caseBase(Command<S, C, A> command) {
					return command;
				}
				@Override public <T, D, P, A> Command<S, C, ?>
				caseFork(Command<S, C, A> command, Context<T, D> parent, P parameter, String binding) {
					return command;
				}
			});
		}

		static <S, C, P, A> Command<?, ?, A> getCommand(Binding<S, C, P, A> binding) {
			return binding.match(new Binding.Match<S, C, P, A, Command<?, ?, A>>() {
				@Override public <T, D> Command<?, ?, A>
				caseEntry(BiFunction<Context<S, C>, P, Tuple<T, D>> delegator, Command<T, D, A> command) {
					return command;
				}
			});
		}

		static <S, C, A> List<Parameter<S, C, ?>> getParameters(Body<S, C, Unit, A> body) {
			return inspectParameters(body).first();
		}

		static <S, C, A> List<Parameter<S, C, ?>> getParameters(Command<S, C, A> command) {
			return command.match(new Command.Match<S, C, A, List<Parameter<S, C, ?>>>() {
				@Override public <P> List<Parameter<S, C, ?>>
				caseNode(Definition<S, C, P> definition, Dispatcher<S, C, P, A> dispatcher, Behavior<S, C, P, A> behavior) {
					return getParameters(definition.body());
				}
			});
		}

		static <S, C, A> List<? extends Parameter<S, C, ?>> getParameters(Parameter<S, C, A> parameter) {
			return parameter.match(new Parameter.Match<S, C, A, List<? extends Parameter<S, C, ?>>>() {
				@Override public List<? extends Parameter<S, C, ?>>
				caseAtomic(Parser<Text, Context<S, C>, Bottom, A> parser, Parser<Text, Context<S, C>, Bottom, List<Completion>> completer) {
					return nil();
				}
				@Override public <X> List<? extends Parameter<S, C, ?>>
				caseNested(Definition<S, C, X> definition, Function<X, Parser<Text, Context<S, C>, Bottom, A>> extend, Parser<Text, Context<S, C>, Bottom, List<Completion>> suggest) {
					return getParameters(definition.body());
				}
				@Override public <X> List<? extends Parameter<S, C, ?>>
				caseUnion(List<Parameter<S, C, X>> parameters, Function<X, Parser<Text, Context<S, C>, Bottom, A>> extend, Parser<Text, Context<S, C>, Bottom, List<Completion>> suggest) {
					return parameters;
				}
			});
		}

		static <S, C, A> List<? extends Binding<S, C, ?, A>> getBindings(Command<S, C, A> command) {
			return command.match(new Command.Match<S, C, A, List<? extends Binding<S, C, ?, A>>>() {
				@Override
				public <P> List<? extends Binding<S, C, ?, A>> caseNode(Definition<S, C, P> definition, Dispatcher<S, C, P, A> dispatcher, Behavior<S, C, P, A> behavior) {
					return dispatcher.bindings();
				}
			});
		}

		static String getSyntax(Command<?, ?, ?> command) {
			CommandSubtype subtype = getSubtype(command);
			StringBuilder builder = new StringBuilder();

			builder.append("<");
			builder.append(command.name());
			builder.append(">");

			List<? extends Parameter<?, ?, ?>> parameters = HelpCommand.getParameters(command);
			if (parameters.length() > 0) {
				builder.append(" ");
				builder.append(String.join(" ", parameters.stream().map(HelpCommand::getSyntax).toArray(CharSequence[]::new)));
			}
			List<? extends Binding<?, ?, ?, ?>> bindings = HelpCommand.getBindings(command);
			if (bindings.length() > 0) {
				builder.append(" ");
				if (bindings.length() > 1) builder.append(subtype.equals(CommandSubtype.Stub) ? "(" : "[");
				builder.append(String.join("|", bindings.stream().map(Binding::binding).toArray(CharSequence[]::new)));
				if (bindings.length() > 1) builder.append(subtype.equals(CommandSubtype.Stub) ? ")" : "]");
			}

			return builder.toString();
		}

		static String getSyntax(Parameter<?, ?, ?> parameter) {
			ParameterSubtype subtype = getSubtype(parameter);
			StringBuilder builder = new StringBuilder();

			builder.append("<");
			builder.append(parameter.name());
			builder.append(">");

			List<? extends Parameter<?, ?, ?>> parameters = HelpCommand.getParameters(parameter);
			if (parameters.isCons()) {
				builder.append(":");
				if (parameters.length() > 1) builder.append("(");
				builder.append(String.join(subtype.equals(ParameterSubtype.Union) ? "|" : " ", parameters.stream().map(HelpCommand::getSyntax).toArray(CharSequence[]::new)));
				if (parameters.length() > 1) builder.append(")");
			}

			return builder.toString();
		}

		static <S, C> Parser<Text, Context<S, C>, Bottom, Tuple<String, Either<Command<?, ?, ?>, Parameter<?, ?, ?>>>> targetParser() {
			return $do(
			$(	getUser()																																	, context ->
			$(	context.match(new Context.Match<S, C, Parser<Text, Context<S, C>, Bottom, Tuple<String, Either<Command<?, ?, ?>, Parameter<?, ?, ?>>>>>() {
					@Override public <A> Parser<Text, Context<S, C>, Bottom, Tuple<String, Either<Command<?, ?, ?>, Parameter<?, ?, ?>>>>
					caseBase(Command<S, C, A> command) {
						return stop();
					}
					@Override public <T, D, P, A> Parser<Text, Context<S, C>, Bottom, Tuple<String, Either<Command<?, ?, ?>, Parameter<?, ?, ?>>>>
					caseFork(Command<S, C, A> command, Context<T, D> parent, P parameter, String binding) {
						return recur(() -> dispatcherParser(getCommand(parent)));
					}
				})																																			))
			);
		}

		static <S, C, T, D, A> Parser<Text, Context<S, C>, Bottom, Tuple<String, Either<Command<?, ?, ?>, Parameter<?, ?, ?>>>> dispatcherParser(Command<T, D, A> command) {
			return command.match(new Command.Match<T, D, A, Parser<Text, Context<S, C>, Bottom, Tuple<String, Either<Command<?, ?, ?>, Parameter<?, ?, ?>>>>>() {
				@Override public <P> Parser<Text, Context<S, C>, Bottom, Tuple<String, Either<Command<?, ?, ?>, Parameter<?, ?, ?>>>>
				caseNode(Definition<T, D, P> definition, Dispatcher<T, D, P, A> dispatcher, Behavior<T, D, P, A> behavior) {
					return recur(() -> dispatcherParser(dispatcher));
				}
			});
		}

		static <S, C, T, D, P, A> Parser<Text, Context<S, C>, Bottom, Tuple<String, Either<Command<?, ?, ?>, Parameter<?, ?, ?>>>> dispatcherParser(Dispatcher<T, D, P, A> dispatcher) {
			return recur(() -> bindingsParser(dispatcher.bindings()));
		}

		static <S, C, T, D, P, A> Parser<Text, Context<S, C>, Bottom, Tuple<String, Either<Command<?, ?, ?>, Parameter<?, ?, ?>>>> bindingsParser(List<Binding<T, D, P, A>> bindings) {
			return $do(
			$(	bindings.foldl(
					(parser, binding) -> choice(replace(string(binding.binding()), binding), parser),
					Parser.<Text, Context<S, C>, Bottom, Binding<T, D, P, A>> ignore()
				)																						, binding ->
			$(	recur(() -> bindingParser(binding))														))
			);
		}

		static <S, C, T, D, P, A> Parser<Text, Context<S, C>, Bottom, Tuple<String, Either<Command<?, ?, ?>, Parameter<?, ?, ?>>>> bindingParser(Binding<T, D, P, A> binding) {
			return choice(
				$do(
				$(	attempt($do(
					$(	spaces()		, () ->
					$(	character('.')	))
					))																			, () ->
				$(	spaces()																	, () ->
				$(	recur(() -> dispatcherParser(getCommand(binding)))							, result ->
				$(	simple(tuple(binding.binding() + "." + result.first(), result.second()))	))))
				),
				$do(
				$(	attempt($do(
					$(	spaces()		, () ->
					$(	character(':')	))
					))																			, () ->
				$(	spaces()																	, () ->
				$(	recur(() -> definitionParser(getCommand(binding)))							, result ->
				$(	simple(tuple(binding.binding() + ":" + result.first(), result.second()))	))))
				),
				simple(tuple(binding.binding(), left(getCommand(binding))))
			);
		}

		static <S, C, T, D, A> Parser<Text, Context<S, C>, Bottom, Tuple<String, Either<Command<?, ?, ?>, Parameter<?, ?, ?>>>> definitionParser(Command<T, D, A> command) {
			return command.match(new Command.Match<T, D, A, Parser<Text, Context<S, C>, Bottom, Tuple<String, Either<Command<?, ?, ?>, Parameter<?, ?, ?>>>>>() {
				@Override public <P> Parser<Text, Context<S, C>, Bottom, Tuple<String, Either<Command<?, ?, ?>, Parameter<?, ?, ?>>>>
				caseNode(Definition<T, D, P> definition, Dispatcher<T, D, P, A> dispatcher, Behavior<T, D, P, A> behavior) {
					return recur(() -> definitionParser(definition));
				}
			});
		}

		static <S, C, T, D, P> Parser<Text, Context<S, C>, Bottom, Tuple<String, Either<Command<?, ?, ?>, Parameter<?, ?, ?>>>> definitionParser(Definition<T, D, P> definition) {
			return recur(() -> parametersParser(getParameters(definition.body())));
		}

		static <S, C, T, D> Parser<Text, Context<S, C>, Bottom, Tuple<String, Either<Command<?, ?, ?>, Parameter<?, ?, ?>>>> parametersParser(List<Parameter<T, D, ?>> parameters) {
			return $do(
			$(	parameters.foldl(
					(parser, parameter) -> choice(replace(string(parameter.name()), parameter), parser),
					Parser.<Text, Context<S, C>, Bottom, Parameter<T, D, ?>> ignore()
				)																							, parameter ->
			$(	recur(() -> parameterParser(parameter))														))
			);
		}

		static <S, C, T, D, A> Parser<Text, Context<S, C>, Bottom, Tuple<String, Either<Command<?, ?, ?>, Parameter<?, ?, ?>>>> parameterParser(Parameter<T, D, A> parameter) {
			return parameter.match(new Parameter.Match<T, D, A, Parser<Text, Context<S, C>, Bottom, Tuple<String, Either<Command<?, ?, ?>, Parameter<?, ?, ?>>>>>() {
				@Override public Parser<Text, Context<S, C>, Bottom, Tuple<String, Either<Command<?, ?, ?>, Parameter<?, ?, ?>>>>
				caseAtomic(Parser<Text, Context<T, D>, Bottom, A> parser, Parser<Text, Context<T, D>, Bottom, List<Completion>> completer) {
					return simple(tuple(parameter.name(), right(parameter)));
				}
				@Override public <X> Parser<Text, Context<S, C>, Bottom, Tuple<String, Either<Command<?, ?, ?>, Parameter<?, ?, ?>>>>
				caseUnion(List<Parameter<T, D, X>> parameters, Function<X, Parser<Text, Context<T, D>, Bottom, A>> extend, Parser<Text, Context<T, D>, Bottom, List<Completion>> suggest) {
					return choice(
						$do(
						$(	attempt($do(
							$(	spaces()		, () ->
							$(	character(':')	))
							))																		, () ->
						$(	spaces()																, () ->
						$(	recur(() -> parametersParser(parameters.map(parameter -> parameter)))	, result ->
						$(	simple(tuple(parameter.name() + ":" + result.first(), result.second()))	))))
						),
						simple(tuple(parameter.name(), right(parameter)))
					);
				}
				@Override public <X> Parser<Text, Context<S, C>, Bottom, Tuple<String, Either<Command<?, ?, ?>, Parameter<?, ?, ?>>>>
				caseNested(Definition<T, D, X> definition, Function<X, Parser<Text, Context<T, D>, Bottom, A>> extend, Parser<Text, Context<T, D>, Bottom, List<Completion>> suggest) {
					return choice(
						$do(
						$(	attempt($do(
							$(	spaces()		, () ->
							$(	character(':')	))
							))																		, () ->
						$(	spaces()																, () ->
						$(	recur(() -> definitionParser(definition))								, result ->
						$(	simple(tuple(parameter.name() + ":" + result.first(), result.second()))	))))
						),
						simple(tuple(parameter.name(), right(parameter)))
					);
				}
			});
		}

		static <S, C> Parser<Text, Context<S, C>, Bottom, List<Completion>> targetCompleter() {
			return $do(
			$(	getUser()																							, context ->
			$(	context.match(new Context.Match<S, C, Parser<Text, Context<S, C>, Bottom, List<Completion>>>() {
					@Override public <A> Parser<Text, Context<S, C>, Bottom, List<Completion>>
					caseBase(Command<S, C, A> command) {
						return stop();
					}
					@Override public <T, D, P, A> Parser<Text, Context<S, C>, Bottom, List<Completion>>
					caseFork(Command<S, C, A> command, Context<T, D> parent, P parameter, String binding) {
						return recur(() -> dispatcherCompleter(getCommand(parent)));
					}
				})																									))
			);
		}

		static <S, C, T, D, A> Parser<Text, Context<S, C>, Bottom, List<Completion>> dispatcherCompleter(Command<T, D, A> command) {
			return command.match(new Command.Match<T, D, A, Parser<Text, Context<S, C>, Bottom, List<Completion>>>() {
				@Override public <P> Parser<Text, Context<S, C>, Bottom, List<Completion>>
				caseNode(Definition<T, D, P> definition, Dispatcher<T, D, P, A> dispatcher, Behavior<T, D, P, A> behavior) {
					return recur(() -> dispatcherCompleter(dispatcher));
				}
			});
		}

		static <S, C, T, D, P, A> Parser<Text, Context<S, C>, Bottom, List<Completion>> dispatcherCompleter(Dispatcher<T, D, P, A> dispatcher) {
			return recur(() -> bindingsCompleter(dispatcher.bindings()));
		}

		static <S, C, T, D, P, A> Parser<Text, Context<S, C>, Bottom, List<Completion>> bindingsCompleter(List<Binding<T, D, P, A>> bindings) {
			return choice($do(
			$(	bindings.foldl(
					(parser, binding) -> choice(replace(string(binding.binding()), binding), parser),
					Parser.<Text, Context<S, C>, Bottom, Binding<T, D, P, A>> ignore()
				)																						, binding ->
			$(	recur(() -> bindingCompleter(binding))													))
			), $do(
			$(	getStream()													, input ->
			$(	getLocation()												, location ->
			$(	simple(list(bindings.stream()
					.map(binding -> text(binding.binding()))
					.filter(input::isPrefixOf)
					.map(completion -> completion(completion, location))
					.toArray(Completion[]::new)
				))															)))
			));
		}

		static <S, C, T, D, P, A> Parser<Text, Context<S, C>, Bottom, List<Completion>> bindingCompleter(Binding<T, D, P, A> binding) {
			return choice(
				$do(
				$(	attempt($do(
					$(	spaces()		, () ->
					$(	character('.')	))
					))														, () ->
				$(	spaces()												, () ->
				$(	recur(() -> dispatcherCompleter(getCommand(binding)))	)))
				),
				$do(
				$(	attempt($do(
					$(	spaces()		, () ->
					$(	character(':')	))
					))														, () ->
				$(	spaces()												, () ->
				$(	recur(() -> definitionCompleter(getCommand(binding)))	)))
				),
				$do(
				$(	spaces()						, () ->
				$(	completer(text("."), text(":"))	))
				)
			);
		}

		static <S, C, T, D, A> Parser<Text, Context<S, C>, Bottom, List<Completion>> definitionCompleter(Command<T, D, A> command) {
			return command.match(new Command.Match<T, D, A, Parser<Text, Context<S, C>, Bottom, List<Completion>>>() {
				@Override public <P> Parser<Text, Context<S, C>, Bottom, List<Completion>>
				caseNode(Definition<T, D, P> definition, Dispatcher<T, D, P, A> dispatcher, Behavior<T, D, P, A> behavior) {
					return recur(() -> definitionCompleter(definition));
				}
			});
		}

		static <S, C, T, D, P> Parser<Text, Context<S, C>, Bottom, List<Completion>> definitionCompleter(Definition<T, D, P> definition) {
			return recur(() -> parametersCompleter(getParameters(definition.body())));
		}

		static <S, C, T, D> Parser<Text, Context<S, C>, Bottom, List<Completion>> parametersCompleter(List<Parameter<T, D, ?>> parameters) {
			return choice($do(
			$(	parameters.foldl(
					(parser, parameter) -> choice(replace(string(parameter.name()), parameter), parser),
					Parser.<Text, Context<S, C>, Bottom, Parameter<T, D, ?>> ignore()
				)																							, parameter ->
			$(	recur(() -> parameterCompleter(parameter))													))
			), $do(
			$(	getStream()													, input ->
			$(	getLocation()												, location ->
			$(	simple(list(parameters.stream()
					.map(parameter -> text(parameter.name()))
					.filter(input::isPrefixOf)
					.map(completion -> completion(completion, location))
					.toArray(Completion[]::new)
				))															)))
			));
		}

		static <S, C, T, D, A> Parser<Text, Context<S, C>, Bottom, List<Completion>> parameterCompleter(Parameter<T, D, A> parameter) {
			return parameter.match(new Parameter.Match<T, D, A, Parser<Text, Context<S, C>, Bottom, List<Completion>>>() {
				@Override public Parser<Text, Context<S, C>, Bottom, List<Completion>>
				caseAtomic(Parser<Text, Context<T, D>, Bottom, A> parser, Parser<Text, Context<T, D>, Bottom, List<Completion>> completer) {
					return simple(nil());
				}
				@Override public <X> Parser<Text, Context<S, C>, Bottom, List<Completion>>
				caseUnion(List<Parameter<T, D, X>> parameters, Function<X, Parser<Text, Context<T, D>, Bottom, A>> extend, Parser<Text, Context<T, D>, Bottom, List<Completion>> suggest) {
					return choice(
						$do(
						$(	attempt($do(
							$(	spaces()		, () ->
							$(	character(':')	))
							))																			, () ->
						$(	spaces()																	, () ->
						$(	recur(() -> parametersCompleter(parameters.map(parameter -> parameter)))	)))
						),
						$do(
						$(	spaces()				, () ->
						$(	completer(text(":"))	))
						)
					);
				}
				@Override public <X> Parser<Text, Context<S, C>, Bottom, List<Completion>>
				caseNested(Definition<T, D, X> definition, Function<X, Parser<Text, Context<T, D>, Bottom, A>> extend, Parser<Text, Context<T, D>, Bottom, List<Completion>> suggest) {
					return choice(
						$do(
						$(	attempt($do(
							$(	spaces()		, () ->
							$(	character(':')	))
							))												, () ->
						$(	spaces()										, () ->
						$(	recur(() -> definitionCompleter(definition))	)))
						),
						$do(
						$(	spaces()				, () ->
						$(	completer(text(":"))	))
						)
					);
				}
			});
		}

		static <S, C, T> Body<S, C, T, Flow<T, Tuple<String, Either<Command<?, ?, ?>, Parameter<?, ?, ?>>>>> helpBody() {
			return define(parameter(
				"target",
				"Help target",
				recur(HelpCommand::targetParser),
				recur(HelpCommand::targetCompleter)
			));
		}
	}

	public static <C, A> Command<CommandSender, C, A> commandHelp(String name, String description) {
		return command(
			name,
			description,
			HelpCommand::helpBody,
			dispatcher(),
			handler((context, parameter) -> rejected(() -> {
				CommandSender sender = context.source();
				String path = parameter.first();

				HelpCommand.sendTitle(sender, "Help: " + path);

				if (parameter.second().isLeft()) {
					Command<?, ?, ?> target = parameter.second().coerceLeft();
					HelpCommand.sendEntry(sender, "Type", "Command");
					HelpCommand.sendEntry(sender, "Subtype", HelpCommand.getSubtype(target).toString());
					HelpCommand.sendEntry(sender, "Description", target.description());
					HelpCommand.sendEntry(sender, "Syntax", HelpCommand.getSyntax(target));
					List<? extends Parameter<?, ?, ?>> parameters = HelpCommand.getParameters(target);
					if (parameters.length() > 0) {
						HelpCommand.sendEntry(sender, "Parameters", String.join(", ", parameters.stream().map(Parameter::name).toArray(CharSequence[]::new)));
					}
					List<? extends Binding<?, ?, ?, ?>> bindings = HelpCommand.getBindings(target);
					if (bindings.length() > 0) {
						HelpCommand.sendEntry(sender, "Bindings", String.join(", ", bindings.stream().map(Binding::binding).toArray(CharSequence[]::new)));
					}
				} else if (parameter.second().isRight()) {
					Parameter<?, ?, ?> target = parameter.second().coerceRight();
					HelpCommand.sendEntry(sender, "Type", "Parameter");
					HelpCommand.sendEntry(sender, "Subtype", HelpCommand.getSubtype(target).toString());
					HelpCommand.sendEntry(sender, "Description", target.description());
					HelpCommand.sendEntry(sender, "Syntax", HelpCommand.getSyntax(target));
					List<? extends Parameter<?, ?, ?>> parameters = HelpCommand.getParameters(target);
					if (parameters.length() > 0) {
						HelpCommand.sendEntry(sender, "Parameters", String.join(", ", parameters.stream().map(Parameter::name).toArray(CharSequence[]::new)));
					}
				}
			}))
		);
	}
}
